﻿using Come.CollectiveOAuth.Config;
using Come.CollectiveOAuth.Models;
using Come.CollectiveOAuth.Utils;
using Microsoft.AspNetCore.Mvc;
using PMS.Core.Caching;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Data.Entities.Customers;
using PMS.Plugin.ExternalAuth.DingTalkScan.Models;
using PMS.Services.Authentication.Externel;
using PMS.Services.Caching;
using PMS.Services.EntityServices.Customers;
using PMS.Services.EntityServices.Settings;
using PMS.Services.SharedKernel;
using PMS.Services.Utils;
using PMS.Web.Framework.CollectiveAuthFactory;
using PMS.Web.Framework.Controllers;
using PMS.Web.Framework.Mvc.Filters;
using System;
using System.ComponentModel;
using System.Security.Claims;

namespace PMS.Plugin.ExternalAuth.DingTalkScan.Controllers
{
    public class DingTalkScanAuthenticationController : BasePluginController
    {
        #region Fields

        private readonly DingTalkScanExternalAuthSettings _dingtalkScanExternalAuthSettings;
        private readonly IAuthenticationPluginManager _authenticationPluginManager;
        private readonly IExternalAuthenticationService _externalAuthenticationService;
        private readonly IStaticCacheManager _cacheManager;
        private readonly ICacheKeyService _cacheKeyService;
        private readonly ICustomerService _customerService;
        private readonly ISettingService _settingService;
        private readonly IStoreContext _storeContext;
        private readonly IWorkContext _workContext;
        private readonly IWebHelper _webHelper;
        private ClientConfig _cachedClientCfg;
        private CollectiveAuthFactory _collectiveAuthFactory;

        #endregion

        #region Ctor

        public DingTalkScanAuthenticationController(DingTalkScanExternalAuthSettings dingtalkScanExternalAuthSettings,
            IAuthenticationPluginManager authenticationPluginManager,
            IExternalAuthenticationService externalAuthenticationService,
            IStaticCacheManager cacheManager,
            ICacheKeyService cacheKeyService,
            ICustomerService customerService,
            ISettingService settingService,
            IStoreContext storeContext,
            IWorkContext workContext,
            IWebHelper webHelper)
        {
            _dingtalkScanExternalAuthSettings = dingtalkScanExternalAuthSettings;
            _authenticationPluginManager = authenticationPluginManager;
            _externalAuthenticationService = externalAuthenticationService;
            _customerService = customerService;
            _cacheKeyService = cacheKeyService;
            _cacheManager = cacheManager;
            _settingService = settingService;
            _storeContext = storeContext;
            _workContext = workContext;
            _webHelper = webHelper;
            _cachedClientCfg = null;
            //DavidLee: Don't create CollectiveAuthFactory Instance in Constructor
            //It Causes Null Value while uing Url instance in ClientCfg
            // new CollectiveAuthFactory(ClientCfg, Come.CollectiveOAuth.Enums.DefaultAuthSourceEnum.DINGTALK_SCAN);
            _collectiveAuthFactory = null; 
        }

        #endregion

        #region Utils

        //以下方法针对钉钉扫码不适用，扫码登录获取的“tmp_auth_code”为临时授权码，只能使用一次
        //要想通过accessToken获取用户信息需要创建网页应用获取appKey及appSecret
        ///// <summary>
        ///// Remote ApiPostCall Getting DingTalk UserId by UnionId
        ///// https://ding-doc.dingtalk.com/document#/org-dev-guide/retrieve-user-information-by-unionid-v2
        ///// </summary>
        ///// <param name="accessToken"></param>
        ///// <param name="unionId"></param>
        ///// <returns></returns>
        //private string DingTalkApiGetUserIdByUnionId(string accessToken, string unionId)
        //{
        //    if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(unionId))
        //        throw new PMSException("DingTalkApiGetUserIdByUnionId: access_token or unionid cannot be empty.", SystemLogLevel.Error);
        //    IDingTalkClient client = new DefaultDingTalkClient(DingTalkScanAuthenticationDefaults.DingTalkApiGetUserId);
        //    OapiUserGetbyunionidRequest unionRequest = new OapiUserGetbyunionidRequest
        //    {
        //        Unionid = unionId
        //    };
        //    OapiUserGetbyunionidResponse apiResponse = client.Execute(unionRequest, accessToken);
        //    if (apiResponse.IsError)
        //        throw new PMSException($"DingTalkApiGetUserIdByUnionId Response Error: ErrCode: {apiResponse.ErrCode}, ErrMsg: {apiResponse.ErrMsg}", SystemLogLevel.Error);
        //    return apiResponse.Result.Userid;
        //}

        //private Customer DingTalkApiGetUserInfoByUserId(string accessToken, string userId)
        //{
        //    if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(userId))
        //        throw new PMSException("DingTalkApiGetUserInfoByUserId: access_token or userid cannot be empty.", SystemLogLevel.Error);
        //    IDingTalkClient client = new DefaultDingTalkClient(DingTalkScanAuthenticationDefaults.DingTalkApiGetUserInfo);
        //    OapiV2UserGetRequest userRequest = new OapiV2UserGetRequest
        //    {
        //        Userid = userId,
        //        Language = _workContext.WorkingLanguage.CultureCode
        //    };
        //    OapiV2UserGetResponse apiResponse = client.Execute(userRequest, accessToken);
        //    if (apiResponse.IsError)
        //        throw new PMSException($"DingTalkApiGetUserInfoByUserId Response Error: ErrCode: {apiResponse.ErrCode}, ErrMsg: {apiResponse.ErrMsg}", SystemLogLevel.Error);
        //    var dingtalkAuthUser = apiResponse.Result;

        //    if (string.IsNullOrEmpty(dingtalkAuthUser.Mobile))
        //        throw new PMSException("DingTalkApiGetUserInfoByUserId UserMobile required", SystemLogLevel.Error);

        //    //此处只做获取到的用户与Customer信息转换，不能直接用来注册
        //    return new Customer
        //    {
        //        Email = string.IsNullOrEmpty(dingtalkAuthUser.Email) ? $"{dingtalkAuthUser.Mobile}@current-store-name.com" : dingtalkAuthUser.Email,
        //        Mobile = dingtalkAuthUser.Mobile,
        //        LoginName = userId,
        //        NickName = dingtalkAuthUser.Name,
        //        HasShoppingCartItems = false,
        //        RequireChangingPwd = false,
        //        FailedLoginAttempts = 0,
        //        CannotLoginUntilDateUtc = null,
        //        IsActive = true,
        //        IsDeleted = false,
        //        LastActivityTimeUtc = DateTime.UtcNow,
        //        CreatedOnTimeUtc = DateTime.UtcNow,
        //        UpdatedOnTimeUtc = DateTime.UtcNow,
        //        LastLoginTimeUtc = DateTime.UtcNow,
        //        LastLoginIpAddress = _webHelper.GetCurrentIpAddress(),
        //        RegisteredInStoreId = _storeContext.CurrentStore.Id
        //    }; 
        //}
        #endregion

        #region Methods

        [AuthorizeCustomer(CustomerAuthorizeType.Admin)]
        public IActionResult Configure()
        {
            var model = new ConfigurationModel
            {
                AppId = _dingtalkScanExternalAuthSettings.AppId,
                AppSecret = _dingtalkScanExternalAuthSettings.AppSecret
            };

            return View("~/Plugins/ExternalAuth.DingTalkScan/Views/Configure.cshtml", model);
        }

        /// <summary>
        /// WebApi Call for ajax Post for Plugin Configure
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [AutoValidateAntiforgeryToken]
        [HttpPost("/api/plugins/dingtalkscan/configure")]
        [AuthorizeCustomer(CustomerAuthorizeType.Admin)]
        public IActionResult Configure([FromBody]ConfigurationModel model)
        {
            if (!ModelState.IsValid || model == null)
                return ErrorJson("Invalid ConfigurationModel for DingTalkScan Configure");

            //save settings
            _dingtalkScanExternalAuthSettings.AppId = model.AppId;
            _dingtalkScanExternalAuthSettings.AppSecret = model.AppSecret;
            _settingService.SaveSetting(_dingtalkScanExternalAuthSettings);

            return SuccessJson("DingTalkScan Plugin Configure Successed.");
        }

        /// <summary>
        /// 构建DingTalk授权Url方法
        /// https://developers.dingtalk.com/document/app/scan-qr-code-to-log-on-to-third-party-websites
        /// </summary>
        /// <returns></returns>
        public IActionResult Authorization()
        {
            var methodIsAvailable = _authenticationPluginManager
                .IsPluginActive(DingTalkScanAuthenticationDefaults.SystemName, _workContext.CurrentCustomer, _storeContext.CurrentStore.Id);

            if (!methodIsAvailable || 
                string.IsNullOrEmpty(_dingtalkScanExternalAuthSettings.AppId) ||
                string.IsNullOrEmpty(_dingtalkScanExternalAuthSettings.AppSecret))
            {
                return ErrorAuthentication(new[] { "插件未启用,请登录后台配置并启用该插件" });
            }

            var request = AuthFactory.getRequest();
            //set Loggedin CustomerGuid into cache if customer logged in
            //for DingTalkScanCallback cannt get logged in customer from _workContext.CurrentCustomer
            string authState = AuthStateUtils.createState();
            if (_workContext.CurrentCustomer != null)
            {
                var key = _cacheKeyService.PrepareKeyForShortTermCache(DingTalkScanAuthenticationDefaults.LoggedinCustomerGuidCacheKey, authState);
                _cacheManager.Set(key, _workContext.CurrentCustomer.CustomerGuid.ToString());
            }
            var authorize = request.authorize(authState);
            return Redirect(authorize);
        }

        /// <summary>
        /// 钉钉callback
        /// </summary>
        /// <param name="authSource"></param>
        /// <param name="authCallback"></param>
        /// <returns></returns>
        public IActionResult DingTalkScanCallback(AuthCallback authCallback)
        {
            var request = AuthFactory.getRequest();
            var authResponse = request.login(authCallback);
            if (!authResponse.ok())
                return ErrorAuthentication(new[] { $"钉钉授权失败: errcode: {authResponse.code}, errmsg: {authResponse.msg}" });
            var userInfo = authResponse.data as AuthUser;
            //userInfo.token.accessCode为扫码登录获取的临时授权码，临时授权码只能使用一次。
            //create external authentication parameters
            var authenticationParameters = new ExternalAuthenticationParameters
            {
                ProviderSystemName = DingTalkScanAuthenticationDefaults.SystemName,
                AccessToken = string.Empty,
                AccessTokenExpiredSeconds = DingTalkScanAuthenticationDefaults.AccessTokenExpireSeconds,
                Email = string.Empty,
                Mobile = string.Empty,
                ExternalIdentifier = userInfo.uuid,
                ExternalIdentifierName = "UnionId",
            };
            authenticationParameters.Claims.Clear();
            authenticationParameters.Claims.Add(new ExternalAuthenticationClaim(ClaimTypes.Name, userInfo.nickname));
            //authenticate DingTalk user
            //DingTalk Authenticated User Only For Backend User Associate
            //DavidLee DingTalkScanCallback cannt get logged in customer from _workContext.CurrentCustomer
            if (_workContext.CurrentCustomer == null && !string.IsNullOrEmpty(authCallback.state))
            {
                var key = _cacheKeyService.PrepareKeyForShortTermCache(DingTalkScanAuthenticationDefaults.LoggedinCustomerGuidCacheKey, authCallback.state);
                string customerGuid = _cacheManager.Get<string>(key, () => null);
                if (!string.IsNullOrEmpty(customerGuid)) 
                    _workContext.CurrentCustomer = _customerService.GetCustomerByGuid((Guid)TypeDescriptor.GetConverter(typeof(Guid)).ConvertFrom(customerGuid));
                _cacheManager.Remove(key);
            }
            return _externalAuthenticationService.Authenticate(authenticationParameters, _workContext.CurrentCustomer, Url.RouteUrl("BackendHome"));
        }

        #endregion

        #region Properties
        protected CollectiveAuthFactory AuthFactory
        {
            get
            {
                if (_collectiveAuthFactory != null)
                    return _collectiveAuthFactory;
                _collectiveAuthFactory = new CollectiveAuthFactory(ClientCfg, Come.CollectiveOAuth.Enums.DefaultAuthSourceEnum.DINGTALK_SCAN);
                return _collectiveAuthFactory;
            }
        }

        protected ClientConfig ClientCfg
        {
            get
            {
                if (_cachedClientCfg != null)
                    return _cachedClientCfg;
                var CallbackUrl = Url.RouteUrl("DingTalkScanCallback");
                if (CallbackUrl.StartsWith("/"))
                    CallbackUrl = _webHelper.GetStoreLocation() + CallbackUrl.TrimStart('/');
                    //CallbackUrl = "http://2t7c3yj.nat.ipyingshe.com/" + CallbackUrl.TrimStart('/');
                _cachedClientCfg = new ClientConfig
                {
                    clientId = _dingtalkScanExternalAuthSettings.AppId,
                    clientSecret = _dingtalkScanExternalAuthSettings.AppSecret,
                    scope = DingTalkScanExternalAuthSettings.ScanScope,
                    redirectUri = System.Web.HttpUtility.UrlEncode(CallbackUrl)
                };
                return _cachedClientCfg;
            }
        }
        #endregion
    }
}
